import { DateTime } from './lib/date'
import { BaseMod } from './mod/base'
import { UniCrypto } from './lib/uni-crypto'
import { getConfig } from './lib/config'
// 应用
export class App extends BaseMod {
  constructor () {
    super()
    this.dbfix = false
    this.tableName = 'opendb-app-list'
  }

  // 获取应用信息
  async getAppByAppid (appId) {
    const appInfo = await this.getCollection(this.tableName).where({
      appid: appId
    }).get()

    return appInfo.data.length > 0 ? appInfo.data[0] : []
  }

  // 获取应用_id
  async getAppIdByAppid (appId) {
    const app = await this.getAppByAppid(appId)
    return app.length > 0 ? app._id : ''
  }
}

// 版本
export class Version extends BaseMod {
  constructor () {
    super()
    this.tableName = 'app-versions'
  }

  // 获取版本信息
  async getVersion (appid, platformId, appVersion) {
    const versionInfo = await this.getCollection(this.tableName).where({
      appid: appid,
      platform_id: platformId,
      version: appVersion
    }).get()

    return versionInfo.data.length > 0 ? versionInfo.data[0] : []
  }

  // 获取版本信息没有则进行创建
  async getVersionAndCreate (appid, platformId, appVersion) {
    const versionInfo = await this.getVersion(appid, platformId, appVersion)
    if (versionInfo.length === 0) {
      if (appVersion.length > 0 && !appVersion.includes('}')) {
        const thisTime = new DateTime().getTime()
        const insertParam = {
          appid: appid,
          platform_id: platformId,
          version: appVersion,
          create_time: thisTime,
          update_time: thisTime
        }
        const res = await this.insert(this.tableName, insertParam)
        if (res && res.id) {
          Object.assign(insertParam, { _id: res.id })
          return insertParam
        }
      }
    }
    return versionInfo
  }

  // 获取版本_id
  async getVersionId (appid, platformId, appVersion) {
    const versionInfo = await this.getVersion(appid, platformId, appVersion)
    return versionInfo.length > 0 ? versionInfo._id : ''
  }
}

// 平台
export class Platform extends BaseMod {
  constructor () {
    super()
    this.tableName = 'app-platforms'
  }

  // 获取平台信息
  async getPlatform (platform, os) {
    const platformCode = this.getPlatformCode(platform, os)
    const platformInfo = await this.getCollection(this.tableName).where({
      code: platformCode
    }).get()

    return platformInfo.data.length > 0 ? platformInfo.data[0] : []
  }

  // 获取平台信息没有则创建
  async getPlatformAndCreate (platform, os) {
    if (!platform) {
      return false
    }
    const platformInfo = await this.getPlatform(platform, os)

    if (platformInfo.length === 0) {
      const platformCode = this.getPlatformCode(platform, os)
      const insertParam = {
        code: platformCode,
        name: platformCode,
        create_time: new DateTime().getTime()
      }
      const res = await this.insert(this.tableName, insertParam)
      if (res && res.id) {
        Object.assign(insertParam, { _id: res.id })
        return insertParam
      }
    }
    return platformInfo
  }

  // 获取平台_id
  async getPlatformId (platform, os) {
    const platformInfo = await this.getPlatform(platform, os)
    return platformInfo.length > 0 ? platformInfo._id : ''
  }

  // 获取平台代码
  getPlatformCode (platform, os) {
    let platformCode = platform
    if (platform === 'n') {
      if (os === 'i') {
        platformCode = 'ios'
      } else {
        platformCode = 'android'
      }
    }
    return platformCode
  }
}

// 渠道
export class Channel extends BaseMod {
  constructor () {
    super()
    this.tableName = 'app-channels'
  }

  // 获取渠道信息
  async getChannel (appid, platformId, channel) {
    const channelInfo = await this.getCollection(this.tableName).where({
      appid: appid,
      platform_id: platformId,
      channel_code: channel
    }).get()

    return channelInfo.data.length > 0 ? channelInfo.data[0] : []
  }

  // 获取渠道信息没有则进行创建
  async getChannelAndCreate (appid, platformId, channel) {
    if (channel.length === 0) {
      return false
    }

    const channelInfo = await this.getChannel(appid, platformId, channel)
    if (channelInfo.length === 0) {
      const thisTime = new DateTime().getTime()
      const insertParam = {
        appid: appid,
        platform_id: platformId,
        channel_code: channel,
        channel_name: channel,
        create_time: thisTime,
        update_time: thisTime
      }
      const res = await this.insert(this.tableName, insertParam)
      if (res && res.id) {
        Object.assign(insertParam, { _id: res.id })
        return insertParam
      }
      
    }
    return channelInfo
  }

  // 获取渠道_id
  async getChannelId (appid, platformId, channel) {
    const channelInfo = await this.getChannel(appid, platformId, channel)
    return channelInfo.length > 0 ? channelInfo._id : ''
  }
}

// 页面
export class Page extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-pages'
  }

  // 获取页面信息
  async getPage (appid, url) {
    const pageInfo = await this.getCollection(this.tableName).where({
      appid: appid,
      path: url
    }).get()

    return pageInfo.data.length > 0 ? pageInfo.data[0] : []
  }

  // 获取页面信息不存在则创建
  async getPageAndCreate (appid, url, title) {
    if(!url) {
      return false
    }
    let baseurl = this.getBaseUrl(url)
    const pageInfo = await this.getPage(appid, baseurl)
    if (pageInfo.length === 0) {
      const thisTime = new DateTime().getTime()
      const insertParam = {
        appid: appid,
        path: baseurl,
        title: title,
        page_params: [],
        create_time: thisTime,
        update_time: thisTime
      }
      const res = await this.insert(this.tableName, insertParam)

      if (res && res.id) {
        Object.assign(insertParam, { _id: res.id })
        return insertParam
      }
    } else if(!pageInfo.title && title) {
        await this.update(this.tableName, {title: title}, {_id: pageInfo._id})
    }

    return pageInfo
  }

  // 获取页面_id
  async getPageId (appid, url) {
    const pageInfo = await this.getPage(appid, url)
    return pageInfo.length > 0 ? pageInfo._id : ''
  }

  getBaseUrl(url) {
    let baseurl = url.split('?')[0]
    if(baseurl.indexOf('/') === 0) {
      baseurl = baseurl.substr(1)
    }
    return baseurl
  }
}

export class StatEvent extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-events'
    this.defaultEvent = getConfig('event') || {
      login: '登录',
      register: '注册',
      click: '点击',
      share: '分享',
      pay_success: '支付成功',
      pay_fail: '支付失败',
    }
  }

  // 获取页面信息
  async getEvent (appid, eventKey) {
    const eventInfo = await this.getCollection(this.tableName).where({
      appid: appid,
      event_key: eventKey
    }).get()

    return eventInfo.data.length > 0 ? eventInfo.data[0] : []
  }

  // 获取页面信息不存在则创建
  async getEventAndCreate (appid, eventKey) {
    const eventInfo = await this.getEvent(appid, eventKey)
    if (eventInfo.length === 0) {
      const thisTime = new DateTime().getTime()
      const insertParam = {
        appid: appid,
        event_key: eventKey,
        event_name: this.defaultEvent[eventKey] ?? '',
        create_time: thisTime,
        update_time: thisTime
      }
      const res = await this.insert(this.tableName, insertParam)

      if (res && res.id) {
        Object.assign(insertParam, { _id: res.id })
        return insertParam
      }
    }

    return eventInfo
  }
}



// 会话日志
export class SessionLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-session-logs'
  }

  // 日志填充
  async fill (params) {
    // 应用信息
    if (!params.ak) {
      return {
        code: 200,
        msg: 'Parameter "ak" not found'
      }
    }

    // 平台信息
    if (!params.ut) {
      return {
        code: 200,
        msg: 'Parameter "ut" not found'
      }
    }

    // 设备信息
    if (!params.uuid) {
      return {
        code: 200,
        msg: 'Parameter "uuid" not found'
      }
    }

    // 页面信息
    const page = new Page()
    const pageInfo = await page.getPageAndCreate(params.ak, params.url, params.ttpj)
    if (pageInfo.length === 0) {
      return {
        code: 300,
        msg: 'Not found this entry page'
      }
    }
    console.log('pageInfo', JSON.stringify(pageInfo))
    const platform = new Platform()
    const dateTime = new DateTime()
    const nowTime = dateTime.getTime()
    const firstVistTime = params.fvts ? dateTime.strToTime(params.fvts) : nowTime
    const lastVistTime = params.lvts ? dateTime.strToTime(params.lvts) : 0
    const fillParams = {
      appid: params.ak,
      version: params.v ?? '',
      platform: platform.getPlatformCode(params.ut, params.p),
      channel: params.ch ?? '',
      // 访问者
      uuid: params.uuid,
      is_first_visit: (!lastVistTime || firstVistTime === lastVistTime) ? 1 : 0,
      first_visit_time: firstVistTime,
      last_visit_time: nowTime,
      visit_count: params.tvc ? parseInt(params.tvc) : 1,
      // 页面相关
      entry_page_id: pageInfo._id,
      exit_page_id: pageInfo._id,
      page_count: 0,
      event_count: 0,
      duration: 1,
      // 版本
      sdk_version: params.mpsdk ?? '',
      platform_version: params.mpv ?? '',
      // 设备相关
      device_os_name: (params.p && params.p === 'i') ? 'ios' : 'android',
      device_os_version: params.sv ?? '',
      device_vendor: params.brand ?? '',
      device_model: params.md ?? '',
      device_language: params.lang ?? '',
      device_pixel_ratio: params.pr ?? '',
      device_window_width: params.ww ?? '',
      device_window_height: params.wh ?? '',
      device_screen_width: params.sw ?? '',
      device_screen_height: params.sh ?? '',
      // 地区相关
      location_ip: '',
      location_latitude: params.lat ? parseFloat(params.lat) : 0,
      location_longitude: params.lng ? parseFloat(params.lng) : 0,
      location_country: params.cn ?? '',
      location_province: params.pn ?? '',
      location_city: params.ct ?? '',
      is_finish: 0,
      create_time: nowTime
    }

    // 查询日志
    const sessionLogInfo = await this.getCollection(this.tableName).where({
      appid: params.ak,
      platform: platform.getPlatformCode(params.ut, params.p),
      uuid: params.uuid,
      is_finish: 0
    }).get()

    let res = {}
    let entryPageId = pageInfo._id
    let eventCount = fillParams.event_count
    if (sessionLogInfo.data.length > 0) {
      const sessionLogInfoData = sessionLogInfo.data[0]
      entryPageId = sessionLogInfoData.entry_page_id
      eventCount = sessionLogInfoData.event_count
      // 最后一次访问时间距现在超过半小时算一次新的会话
      const accessTime = nowTime - sessionLogInfoData.first_visit_time
      const sessionTime = nowTime - sessionLogInfoData.last_visit_time
      console.log('sessionTime', sessionTime)
      const accessSenconds = accessTime > 1000 ? parseInt(accessTime / 1000) : 1
      if (sessionTime >= 1800000) {
        console.log('session log time expired', sessionTime)
        res = await this.update(this.tableName, {
          is_finish: 1
        }, {
          _id: sessionLogInfoData._id
        })
        res = await this.insert(this.tableName, fillParams)
      } else {
        let pageLog = new PageLog()
        console.log('session log pageLogInfo condition', JSON.stringify({
          session_id: sessionLogInfoData._id,
          current_page_id: pageInfo._id
        }))
        let pageLogInfo = await this.getCollection(pageLog.tableName).where({
          session_id: sessionLogInfoData._id,
          current_page_id: pageInfo._id
        }).count()
        console.log('session log pageLogInfo', JSON.stringify(pageLogInfo))
        let pageCount = sessionLogInfoData.page_count
        if (!pageLogInfo || pageLogInfo.total === 0) {
          pageCount ++
        }
        res = await this.update(this.tableName, {
          last_visit_time: nowTime,
          duration: accessSenconds,
          exit_page_id: pageInfo._id,
          page_count: pageCount
        }, {
          _id: sessionLogInfoData._id
        })
        res.id = sessionLogInfoData._id
      }
    } else {
      console.log('add session log', JSON.stringify(fillParams))
      res = await this.insert(this.tableName, fillParams)
    }

    if (res && res.id) {
      return {
        code: 0,
        msg: 'success',
        data: {
          pageId: pageInfo._id,
          sessionLogId: res.id,
          entryPageId: entryPageId,
          eventCount: eventCount
        }
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }
}

// 页面日志
export class PageLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-page-logs'
  }

  // 日志填充
  async fill (params) {
    if(['3', '4'].includes(params.lt) && !params.url && params.urlref) {
      params.url = params.urlref
    }
    // 会话日志
    const sessionLog = new SessionLog()
    const sessionLogInfo = await sessionLog.fill(params)
    if (sessionLogInfo.code) {
      return sessionLogInfo
    }
    console.log('sessionLogInfo', JSON.stringify(sessionLogInfo))

    // 上级页面信息
    const page = new Page()
    const referPageInfo = await page.getPageAndCreate(params.ak, params.urlref, params.ttpj)
    if (referPageInfo.length === 0) {
      referPageInfo._id = ''
    }

    // 填充数据
    const platform = new Platform()
    const dateTime = new DateTime()
    const fillParams = {
      appid: params.ak,
      version: params.v ?? '',
      platform: platform.getPlatformCode(params.ut, params.p),
      channel: params.ch ?? '',
      uuid: params.uuid,
      session_id: sessionLogInfo.data.sessionLogId,
      current_page_id: sessionLogInfo.data.pageId,
      previous_page_id: referPageInfo._id,
      previous_page_duration: params.urlref_ts ? parseInt(params.urlref_ts) : 0,
      previous_page_is_entry: sessionLogInfo.data.pageId === sessionLogInfo.data.entryPageId ? 1 : 0,
      create_time: dateTime.getTime()
    }

    const res = await this.insert(this.tableName, fillParams)
    if (res && res.id) {
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }
}


// 事件日志
export class EventLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-event-logs'
  }

  // 日志填充
  async fill (params) {
    // 会话日志
    const sessionLog = new SessionLog()
    const sessionLogInfo = await sessionLog.fill(params)
    if (sessionLogInfo.code) {
      return sessionLogInfo
    }

    //事件
    const event = new StatEvent()
    let eventInfo = await event.getEventAndCreate(params.ak, params.e_n)

    // 填充数据
    const platform = new Platform()
    const dateTime = new DateTime()
    const fillParams = {
      appid: params.ak,
      version: params.v ?? '',
      platform: platform.getPlatformCode(params.ut, params.p),
      channel: params.ch ?? '',
      uuid: params.uuid,
      session_id: sessionLogInfo.data.sessionLogId,
      page_id: sessionLogInfo.data.pageId,
      event_key: eventInfo.event_key,
      param: params.e_v ?? '',
      create_time: dateTime.getTime()
    }

    if(fillParams.event_key === 'share') {
      let shareLog = new ShareLog()
      await shareLog.fill(params)
    }

    const res = await this.insert(this.tableName, fillParams)
    if (res && res.id) {
      await this.update(sessionLog.tableName,{event_count: sessionLogInfo.data.eventCount + 1},{_id: sessionLogInfo.data.sessionLogId})
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }
}

// 错误日志
export class ErrorLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-error-logs'
  }

  // 日志填充
  async fill (params) {
    // 填充数据
    const platform = new Platform()
    const dateTime = new DateTime()
    const fillParams = {
      appid: params.ak,
      version: params.v ?? '',
      platform: platform.getPlatformCode(params.ut, params.p),
      channel: params.ch ?? '',
      uuid: params.uuid,
      sdk_version: params.mpsdk ?? '',
      platform_version: params.mpv ?? '',
      error_msg: params.em ?? '',
      error_hash: new UniCrypto().md5(params.em),
      create_time: dateTime.getTime()
    }

    const res = await this.insert(this.tableName, fillParams)
    if (res && res.id) {
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }
}



//分享日志
export class ShareLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-share-logs'
  }

  // 日志填充
  async fill (params) {
    // 会话日志
    const sessionLog = new SessionLog()
    const sessionLogInfo = await sessionLog.fill(params)
    if (sessionLogInfo.code) {
      return sessionLogInfo
    }

    // 填充数据
    const platform = new Platform()
    const dateTime = new DateTime()
    const fillParams = {
      appid: params.ak,
      version: params.v ?? '',
      platform: platform.getPlatformCode(params.ut, params.p),
      channel: params.ch ?? '',
      uuid: params.uuid,
      session_id: sessionLogInfo.data.sessionLogId,
      page_id: sessionLogInfo.data.pageId,
      create_time: dateTime.getTime()
    }

    const res = await this.insert(this.tableName, fillParams)
    if (res && res.id) {
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }
}




// 日志结果统计
export class StatResult extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-result'
    this.platforms = this.channels = this.versions = []
  }

  // 数据统计
  async stat (type, date, reset) {
    let allowedType = ['hour','day', 'week', 'month']
    if(!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    this.fillType = type
    let dateTime = new DateTime()
    let dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime

    console.log('dimension time', this.startTime + '--' + this.endTime)

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        dimension: this.fillType,
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('log have existed')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }

    // 数据获取
    this.sessionLog = new SessionLog()
    const statRes = await this.getCollection(this.sessionLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        uuid: 1,
        is_first_visit: 1,
        page_count: 1,
        event_count: 1,
        duration: 1,
        create_time: 1
      })
      .match({
        create_time: { $gte: this.startTime, $lte: this.endTime }
      })
      .group({
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel'
        },
        new_user_count: { $sum: '$is_first_visit' },
	      page_view_count: { $sum: '$page_count' },
	      total_duration: { $sum: '$duration' },
        session_times: { $sum: 1 }
      })
      .end()

    let res = {
      code: 0,
      msg: 'success'
    }
    console.log('statRes', JSON.stringify(statRes))
    if (statRes.data.length > 0) {
      for (const i in statRes.data) {
        res = await this.fill(statRes.data[i])
      }
    }
    return res
  }

  // 基础数据填充
  async fill (data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      console.log('platformInfo', JSON.stringify(platformInfo))
    }

    // 渠道信息
    let channelInfo = null
    if (this.channels && this.channels[data._id.channel]) {
      channelInfo = this.channels[data._id.channel]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[data._id.channel] = channelInfo
      console.log('channelInfo', JSON.stringify(channelInfo))
    }

    // 版本信息
    let versionInfo = null
    if (this.versions && this.versions[data._id.version]) {
      versionInfo = this.versions[data._id.version]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[data._id.version] = versionInfo
      console.log('versionInfo', JSON.stringify(versionInfo))
    }

    // 访问用户数统计
    const matchCondition = data._id
    Object.assign(matchCondition, {
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })
    const statVisitUserRes = await this.getCollection(this.sessionLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        uuid: 1,
        create_time: 1
      })
      .match(matchCondition)
      .group({
        _id: {
          uuid: '$uuid'
        }
      })
      .group({
        _id: {},
        total_users: { $sum: 1 }
      })
      .end()
    let activeUserCount = 0
    if (statVisitUserRes.data.length > 0) {
      activeUserCount = statVisitUserRes.data[0].total_users
    }

    // 错误数量统计
    const errorLog = new ErrorLog()
    const statErrorRes = await this.getCollection(errorLog.tableName).where(matchCondition).count()
    let errorCount = 0
    if (statErrorRes && statErrorRes.total > 0) {
      errorCount = statErrorRes.total
    }

    // 次均停留时长
    let avgSessionTime = 0
    if (data.total_duration > 0 && data.session_times > 0) {
      avgSessionTime = Math.round(data.total_duration / data.session_times)
    }

    // 人均停留时长
    let avgUserTime = 0
    if (data.total_duration > 0 && activeUserCount > 0) {
      avgUserTime = Math.round(data.total_duration / activeUserCount)
    }

    // 跳出率
    let bounceTimes = 0
    const bounceRes = await this.getCollection(this.sessionLog.tableName).where({
      ...matchCondition,
      $where: 'this.entry_page_id.toString()===this.exit_page_id.toString()'
    }).count()
    if (bounceRes && bounceRes.total > 0) {
      bounceTimes = bounceRes.total
    }
    let bounceRate = 0
    if (bounceTimes > 0 && data.session_times > 0) {
      bounceRate = bounceTimes * 100 / data.session_times
      bounceRate.toFixed(2)
    }

    // 打开次数
    const launchCount = 0
    const pageLog = new PageLog()
    const openCondition = matchCondition
    Object.assign(openCondition, { previous_page_id: '' })
    const pageLogRes = await this.getCollection(pageLog.tableName).where(openCondition).count()
    if (pageLogRes && pageLogRes.total > 0) {
      launchCount = pageLogRes.total
    }

    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      new_user_count: data.new_user_count,
      active_user_count: activeUserCount,
      app_launch_count: launchCount,
      page_visit_count: data.page_view_count,
      error_count: errorCount,
      duration: data.total_duration,
      avg_session_time: avgSessionTime,
      avg_user_time: avgUserTime,
      bounce_rate: bounceRate,
      retention: {},
      dimension: this.fillType,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }
    const res = await this.insert(this.tableName, insertParams)
    console.log('save res', JSON.stringify(res))
    if (res && res.id) {
      return {
        code: 0,
        msg: 'have done'
      }
    } else {
      console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
      return {
        code: 400,
        msg: 'log save error'
      }
    }
  }

  // 留存数据统计
  async retentionStat (type, date) {
    date = date ?? new DateTime().getTimeBySetDays(-1)
    let allowedType = ['day', 'week', 'month']
    if(!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    let days = []
    switch(type) {
      case 'week':
      case 'month':
        days = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        break
      default:
        days = [1, 2, 3, 4, 5, 6, 7, 14, 30]
        break
    }
    let res = {
      code: 0,
      msg: 'success'
    }
    for (const day in days) {
      res = await this.retentionFill(type, days[day], date)
    }
    return res
  }

  // 留存数据填充
  async retentionFill (type, day, date) {
    const dateTime = new DateTime()
    const {
      startTime,
      endTime
    } = dateTime.getTimeDimensionByType(type, 0 - day, date)

    if(!startTime || !endTime) {
      return {
        code: 1001,
        msg: 'The statistic time get failed'
      }
    }

    //截止时间范围
    const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)

    // 获取当时批次的统计日志
    const resultLogRes = await this.getCollection(this.tableName).where({
      dimension: type,
      start_time: startTime,
      end_time: endTime
    }).get()

    if (!resultLogRes || resultLogRes.data.length === 0) {
      console.log('Not found this log --' + type + ':' + day + ', start:' + startTime + ',endTime:' + endTime)
      return {
        code: 1000,
        msg: 'Not found this log'
      }
    }
   
    const sessionLog = new SessionLog()
    const platform = new Platform()
    const channel = new Channel()
    const version = new Version()
    let res = null
    for(let resultIndex in resultLogRes.data) {
      let resultLog = resultLogRes.data[resultIndex]
      // 平台信息
      let platformInfo = null
      if (this.platforms && this.platforms[resultLog.platform_id]) {
        platformInfo = this.platforms[resultLog.platform_id]
      } else {
        platformInfo = await this.getById(platform.tableName, resultLog.platform_id)
        if (!platformInfo || platformInfo.length === 0) {
          platformInfo.code = ''
        }
        this.platforms[resultLog.platform_id] = platformInfo
        console.log('platformInfo', JSON.stringify(platformInfo))
      }
      // 渠道信息
      let channelInfo = null
      if (this.channels && this.channels[resultLog.channel_id]) {
        channelInfo = this.channels[resultLog.channel_id]
      } else {
        channelInfo = await this.getById(channel.tableName, resultLog.channel_id)
        if (!channelInfo || channelInfo.length === 0) {
          channelInfo.channel_code = ''
        }
        this.channels[resultLog.channel_id] = channelInfo
        console.log('channelInfo', JSON.stringify(channelInfo))
      }
      // 版本信息
      let versionInfo = null
      if (this.versions && this.versions[resultLog.version_id]) {
        versionInfo = this.versions[resultLog.version_id]
      } else {
        versionInfo = await this.getById(version.tableName, resultLog.version_id)
        if (!versionInfo || versionInfo.length === 0) {
          versionInfo.version = ''
        }
        this.versions[resultLog.version_id] = versionInfo
        console.log('versionInfo', JSON.stringify(versionInfo))
      }
      //获取该批次的活跃用户数
      const activeUserRes = await this.getCollection(sessionLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        uuid: 1,
        create_time: 1
      })
      .match({
        appid: resultLog.appid,
        version: versionInfo.version,
        platform: platformInfo.code,
        channel: channelInfo.channel_code,
        create_time: { $gte: startTime, $lte: endTime }
      })
      .group({
        _id: {
          uuid: '$uuid'
        }
      })
      .end()
      console.log('activeUserRes', JSON.stringify(activeUserRes))
      let activeUserRate = 0
      let activeUsers = 0
      if (activeUserRes && activeUserRes.data.length > 0) {
        const thisDayActiveUsers = activeUserRes.data.length
        const thisDayActiveUuids = []
        for (const tau in activeUserRes.data) {
          thisDayActiveUuids.push(activeUserRes.data[tau]._id.uuid)
        }
        console.log('thisDayActiveUuids', JSON.stringify(thisDayActiveUuids))
        //留存活跃用户数
        const retentionActiveUserRes = await this.getCollection(sessionLog.tableName)
          .aggregate()
          .project({
            appid: 1,
            version: 1,
            platform: 1,
            channel: 1,
            uuid: 1,
            create_time: 1
          })
          .match({
            appid: resultLog.appid,
            version: versionInfo.version,
            platform: platformInfo.code,
            channel: channelInfo.channel_code,
            uuid: { $in: thisDayActiveUuids },
            create_time: { $gte: lastTimeInfo.startTime, $lte: lastTimeInfo.endTime }
          })
          .group({
            _id: {
              uuid: '$uuid'
            }
          })
          .end()
          console.log('retentionActiveUserRes', JSON.stringify(retentionActiveUserRes))
        if (retentionActiveUserRes && retentionActiveUserRes.data.length > 0) {
          // 活跃用户留存数
          activeUsers = retentionActiveUserRes.data.length
          // 活跃用户留存率
          activeUserRate = (activeUsers * 100 / thisDayActiveUsers).toFixed(2)
        }
      }

      // 现在新增用户数
      const newUserRes = await this.getCollection(sessionLog.tableName)
        .aggregate()
        .project({
          appid: 1,
          version: 1,
          platform: 1,
          channel: 1,
          is_first_visit: 1,
          uuid: 1,
          create_time: 1
        })
        .match({
          appid: resultLog.appid,
          version: versionInfo.version,
          platform: platformInfo.code,
          channel: channelInfo.channel_code,
          is_first_visit: 1,
          create_time: { $gte: startTime, $lte: endTime }
        })
        .group({
          _id: {
            uuid: '$uuid'
          }
        })
        .end()
      let newUserRate = 0
      let newUsers = 0
      if (newUserRes && newUserRes.data.length > 0) {
        const thisDayNewUsers = newUserRes.data.length
        const thisDayNewUuids = []
        for (const tau in newUserRes.data) {
          thisDayNewUuids.push(newUserRes.data[tau]._id.uuid)
        }

        // 现在的活跃用户数
        const retentionNewUserRes = await this.getCollection(sessionLog.tableName)
          .aggregate()
          .project({
            appid: 1,
            version: 1,
            platform: 1,
            channel: 1,
            is_first_visit: 1,
            uuid: 1,
            create_time: 1
          })
          .match({
            appid: resultLog.appid,
            version: versionInfo.version,
            platform: platformInfo.code,
            channel: channelInfo.channel_code,
            is_first_visit: 1,
            uuid: { $in: thisDayNewUuids },
            create_time: { $gte: lastTimeInfo.startTime, $lte: lastTimeInfo.endTime }
          })
          .group({
            _id: {
              uuid: '$uuid'
            }
          })
          .end()
        if (retentionNewUserRes && retentionNewUserRes.data.length > 0) {
          // 活跃用户留存数
          newUsers = retentionNewUserRes.data.length
          // 活跃用户留存率
          newUserRate = (newUsers * 100 / thisDayNewUsers).toFixed(2)
        }
      }

      //数据更新
      const retentionData = resultLogRes.data[0].retention
      let dataKey = type.substr(0, 1) + day
      retentionData.active_user = retentionData.active_user ? retentionData.active_user : {}
      retentionData.active_user[dataKey] = {
        user_count: activeUsers,
        user_rate: activeUserRate
      }
      retentionData.new_user = retentionData.new_user ? retentionData.new_user : {},
      retentionData.new_user[dataKey] = {
        user_count: newUsers,
        user_rate: newUserRate
      }

      res = await this.update(this.tableName, { retention: retentionData }, { _id: resultLog._id })
    }
    
    if (res && res.id) {
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'retention data update failed'
      }
    }
  }
}




// 页面结果统计
export class PageResult extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-page-result'
    this.platforms = this.channels = this.versions = []
  }

  // 数据统计
  async stat (type, date, reset) {
    let allowedType = ['hour','day', 'week', 'month']
    if(!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    this.fillType = type
    let dateTime = new DateTime()
    let dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime

    console.log('dimension time', this.startTime + '--' + this.endTime)

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('log have exists')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }

    // 数据获取
    this.pageLog = new PageLog()
    const statRes = await this.getCollection(this.pageLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        current_page_id: 1,
        create_time: 1
      })
      .match({
        create_time: { $gte: this.startTime, $lte: this.endTime }
      })
      .group({
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel',
          current_page_id: '$current_page_id'
        },
        visit_times: { $sum: 1 }
      })
      .end()

    let res = {
      code: 0,
      msg: 'success'
    }
    console.log('Page statRes', JSON.stringify(statRes))
    if (statRes.data.length > 0) {
      for (const i in statRes.data) {
        res = await this.fill(statRes.data[i])
      }
    }
    return res
  }

  async fill(data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      console.log('platformInfo', JSON.stringify(platformInfo))
    }

    // 渠道信息
    let channelInfo = null
    if (this.channels && this.channels[data._id.channel]) {
      channelInfo = this.channels[data._id.channel]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[data._id.channel] = channelInfo
      console.log('channelInfo', JSON.stringify(channelInfo))
    }

    // 版本信息
    let versionInfo = null
    if (this.versions && this.versions[data._id.version]) {
      versionInfo = this.versions[data._id.version]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[data._id.version] = versionInfo
      console.log('versionInfo', JSON.stringify(versionInfo))
    }

    //当前页面访问人数
    const matchCondition = data._id
    Object.assign(matchCondition, {
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })
    console.log('matchCondition', JSON.stringify(matchCondition))
    const statPageUserRes = await this.getCollection(this.pageLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        current_page_id: 1,
        create_time: 1
      })
      .match(matchCondition)
      .group({
        _id: {
          uuid: '$uuid'
        }
      })
      .group({
        _id: {},
        total_users: { $sum: 1 }
      })
      .end()
    let pageVisitUsers = 0
    if (statPageUserRes.data.length > 0) {
      pageVisitUsers = statPageUserRes.data[0].total_users
    }

    //退出次数
    let sessionLog = new SessionLog()
    let existTimes = 0
    const existRes = await this.getCollection(sessionLog.tableName).where({
      appid: data._id.appid,
      version: data._id.version,
      platform: data._id.platform,
      channel: data._id.channel,
      exit_page_id: data._id.current_page_id,
      create_time: { $gte: this.startTime, $lte: this.endTime }
    }).count()
    if (existRes && existRes.total > 0) {
      existTimes = existRes.total
    }

    //访问时长
    const statPageDurationRes = await this.getCollection(this.pageLog.tableName)
    .aggregate()
    .project({
      appid: 1,
      version: 1,
      platform: 1,
      channel: 1,
      previous_page_id: 1,
      previous_page_duration: 1,
      create_time: 1
    })
    .match({
      appid: data._id.appid,
      version: data._id.version,
      platform: data._id.platform,
      channel: data._id.channel,
      previous_page_id: data._id.current_page_id,
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })
    .group({
      _id: {},
      total_duration: { $sum: "$previous_page_duration" }
    })
    .end()
    let totalDuration = 0
    if (statPageDurationRes.data.length > 0) {
      totalDuration = statPageDurationRes.data[0].total_duration
    }

    //分享次数
    const shareLog = new ShareLog()
    const statShareRes = await this.getCollection(shareLog.tableName)
    .aggregate()
    .project({
      appid: 1,
      version: 1,
      platform: 1,
      channel: 1,
      page_id: 1,
      create_time: 1
    })
    .match({
      appid: data._id.appid,
      version: data._id.version,
      platform: data._id.platform,
      channel: data._id.channel,
      page_id: data._id.current_page_id,
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })
    .group({
      _id: {},
      share_count: { $sum: 1 }
    })
    .end()
    let shareCount = 0
    if (statShareRes.data.length > 0) {
      shareCount = statShareRes.data[0].share_count
    }

    //作为入口页的总次数和总访问时长
    const statPageEntryCountRes = await this.getCollection(this.pageLog.tableName)
    .aggregate()
    .project({
      appid: 1,
      version: 1,
      platform: 1,
      channel: 1,
      previous_page_id: 1,
      previous_page_duration: 1,
      previous_page_is_entry: 1,
      create_time: 1
    })
    .match({
      appid: data._id.appid,
      version: data._id.version,
      platform: data._id.platform,
      channel: data._id.channel,
      previous_page_id: data._id.current_page_id,
      previous_page_is_entry: 1,
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })
    .group({
      _id: {},
      entry_count: { $sum: 1},
      entry_duration: { $sum: "$previous_page_duration" }
    })
    .end()
    let entryCount = 0
    let entryDuration = 0
    if (statPageEntryCountRes.data.length > 0) {
      entryCount = statPageEntryCountRes.data[0].entry_count
      entryDuration = statPageEntryCountRes.data[0].entry_duration
    }

    //作为入口页的总人数
    const statPageEntryUsersRes = await this.getCollection(this.pageLog.tableName)
    .aggregate()
    .project({
      appid: 1,
      version: 1,
      platform: 1,
      channel: 1,
      uuid: 1,
      previous_page_id: 1,
      previous_page_is_entry: 1,
      create_time: 1
    })
    .match({
      appid: data._id.appid,
      version: data._id.version,
      platform: data._id.platform,
      channel: data._id.channel,
      previous_page_id: data._id.current_page_id,
      previous_page_is_entry: 1,
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })
    .group({
      _id: {uuid:"$uuid"}
    })
    .group({
      _id: {},
      entry_users: { $sum: 1}
    })
    .end()
    let entryUsers = 0
    if (statPageEntryUsersRes.data.length > 0) {
      entryUsers = statPageEntryUsersRes.data[0].entry_users
    }

    //跳出率
    let bounceTimes = 0
    const bounceRes = await this.getCollection(sessionLog.tableName).where({
      appid: data._id.appid,
      version: data._id.version,
      platform: data._id.platform,
      channel: data._id.channel,
      entry_page_id: data._id.current_page_id,
      exit_page_id: data._id.current_page_id,
      create_time: { $gte: this.startTime, $lte: this.endTime }
    }).count()
    if (bounceRes && bounceRes.total > 0) {
      bounceTimes = bounceRes.total
    }
    let bounceRate = 0
    if (bounceTimes > 0 && data.visit_times > 0) {
      bounceRate = bounceTimes * 100 / data.visit_times
      bounceRate.toFixed(2)
    }

    //数据填充
    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      page_id: data._id.current_page_id,
      visit_times: data.visit_times,
      visit_users: pageVisitUsers,
      exit_times: existTimes,
      duration: totalDuration,
      share_count: shareCount,
      entry_users: entryUsers,
      entry_count: entryCount,
      entry_duration: entryDuration,
      bounce_rate: bounceRate,
      dimension: this.fillType,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }
    const res = await this.insert(this.tableName, insertParams)
    console.log('save res', JSON.stringify(res))
    if (res && res.id) {
      return {
        code: 0,
        msg: 'have done'
      }
    } else {
      console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
      return {
        code: 400,
        msg: 'log save error'
      }
    }

  }
}


//事件结果统计
export class EventResult extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-event-result'
    this.platforms = this.channels = this.versions = []
  }

   // 数据统计
   async stat (type, date, reset) {
    let allowedType = ['hour','day', 'week', 'month']
    if(!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    this.fillType = type
    let dateTime = new DateTime()
    let dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime

    console.log('dimension time', this.startTime + '--' + this.endTime)

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('log have existed')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }
    
    // 数据获取
    this.eventLog = new EventLog()
    const statRes = await this.getCollection(this.eventLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        event_key: 1,
        uuid: 1,
        create_time: 1
      })
      .match({
        create_time: { $gte: this.startTime, $lte: this.endTime }
      })
      .group({
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel',
          event_key:'$event_key'
        },
        event_count: { $sum: 1 }
      })
      .end()

    let res = {
      code: 0,
      msg: 'success'
    }
    console.log('statRes', JSON.stringify(statRes))
    if (statRes.data.length > 0) {
      for (const i in statRes.data) {
        res = await this.fill(statRes.data[i])
      }
    }
    return res

  }

  async fill(data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      console.log('platformInfo', JSON.stringify(platformInfo))
    }

    // 渠道信息
    let channelInfo = null
    if (this.channels && this.channels[data._id.channel]) {
      channelInfo = this.channels[data._id.channel]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[data._id.channel] = channelInfo
      console.log('channelInfo', JSON.stringify(channelInfo))
    }

    // 版本信息
    let versionInfo = null
    if (this.versions && this.versions[data._id.version]) {
      versionInfo = this.versions[data._id.version]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[data._id.version] = versionInfo
      console.log('versionInfo', JSON.stringify(versionInfo))
    }

    //触发事件用户数统计
    const matchCondition = data._id
    Object.assign(matchCondition, {
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })
    console.log('matchCondition', JSON.stringify(matchCondition))
    const statEventUserRes = await this.getCollection(this.eventLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        event_key: 1,
        uuid: 1,
        create_time: 1
      })
      .match(matchCondition)
      .group({
        _id: {
          uuid: '$uuid'
        }
      })
      .group({
        _id: {},
        total_users: { $sum: 1 }
      })
      .end()
    let eventUserCount = 0
    if (statEventUserRes.data.length > 0) {
      eventUserCount = statEventUserRes.data[0].total_users
    }

    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      event_key: data._id.event_key,
      event_count: data.event_count,
      user_count: eventUserCount,
      dimension: this.fillType,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }
    const res = await this.insert(this.tableName, insertParams)
    console.log('save res', JSON.stringify(res))
    if (res && res.id) {
      return {
        code: 0,
        msg: 'have done'
      }
    } else {
      console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
      return {
        code: 400,
        msg: 'log save error'
      }
    }
  }
}


//错误结果统计
export class ErrorResult extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-error-result'
    this.platforms = this.channels = this.versions = []
  }

   // 数据统计
   async stat (type, date, reset) {
    let allowedType = ['hour','day', 'week', 'month']
    if(!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    this.fillType = type
    let dateTime = new DateTime()
    let dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime

    console.log('dimension time', this.startTime + '--' + this.endTime)

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('log have existed')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }
    
    // 数据获取
    this.errorLog = new ErrorLog()
    const statRes = await this.getCollection(this.errorLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        hash: 1,
        create_time: 1
      })
      .match({
        create_time: { $gte: this.startTime, $lte: this.endTime }
      })
      .group({
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel',
          hash:'$hash'
        },
        error_count: { $sum: 1 }
      })
      .end()

    let res = {
      code: 0,
      msg: 'success'
    }
    console.log('statRes', JSON.stringify(statRes))
    if (statRes.data.length > 0) {
      for (const i in statRes.data) {
        res = await this.fill(statRes.data[i])
      }
    }
    return res

  }

  async fill(data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      console.log('platformInfo', JSON.stringify(platformInfo))
    }

    // 渠道信息
    let channelInfo = null
    if (this.channels && this.channels[data._id.channel]) {
      channelInfo = this.channels[data._id.channel]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[data._id.channel] = channelInfo
      console.log('channelInfo', JSON.stringify(channelInfo))
    }

    // 版本信息
    let versionInfo = null
    if (this.versions && this.versions[data._id.version]) {
      versionInfo = this.versions[data._id.version]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[data._id.version] = versionInfo
      console.log('versionInfo', JSON.stringify(versionInfo))
    }

    //最后一次报错事件
    const matchCondition = data._id
    Object.assign(matchCondition, {
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })
    const lastErrorLog = await this.getCollection(this.errorLog.tableName).where(matchCondition).orderBy('create_time', 'desc').limit(1).get()
    let lastErrorTime = ''
    if(lastErrorLog && lastErrorLog.data.length > 0) {
      lastErrorTime = lastErrorLog.data[0].create_time
    }

    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      hash: data._id.hash,
      count: data.error_count,
      last_time: lastErrorTime,
      dimension: this.fillType,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }
    const res = await this.insert(this.tableName, insertParams)
    console.log('save res', JSON.stringify(res))
    if (res && res.id) {
      return {
        code: 0,
        msg: 'have done'
      }
    } else {
      console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
      return {
        code: 400,
        msg: 'log save error'
      }
    }
  }
}



//用户忠诚度统计
export class Loyalty extends BaseMod{
  constructor () {
    super()
    this.tableName = 'stat-loyalty-result'
    this.platforms = this.channels = this.versions = []
  }
  
  //统计
  async stat(type, date, reset) {
    date = date ?? new DateTime().getTimeBySetDays(-1)
    let allowedType = ['day']
    if(!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }

    this.fillType = type
    let dateTime = new DateTime()
    let dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('log have existed')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }
    
    // 数据获取
    this.sessionLog = new SessionLog()
    const statRes = await this.getCollection(this.sessionLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        page_count: 1,
        duration: 1,
        create_time: 1
      })
      .match({
        create_time: { $gte: this.startTime, $lte: this.endTime }
      })
      .group({
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel',
        },
        page_count_sum: { $sum: "$page_count" },
        duration_sum: { $sum: "$duration" }
      })
      .end()

    let res = {
      code: 0,
      msg: 'success'
    }
    console.log('statRes', JSON.stringify(statRes))
    if (statRes.data.length > 0) {
      for (const i in statRes.data) {
        res = await this.fill(statRes.data[i])
      }
    }
    return res

  }
  
  //数据填充
  async fill(data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      console.log('platformInfo', JSON.stringify(platformInfo))
    }

    // 渠道信息
    let channelInfo = null
    if (this.channels && this.channels[data._id.channel]) {
      channelInfo = this.channels[data._id.channel]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[data._id.channel] = channelInfo
      console.log('channelInfo', JSON.stringify(channelInfo))
    }

    // 版本信息
    let versionInfo = null
    if (this.versions && this.versions[data._id.version]) {
      versionInfo = this.versions[data._id.version]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[data._id.version] = versionInfo
      console.log('versionInfo', JSON.stringify(versionInfo))
    }

    //访问深度-用户数统计和访问次数
    let pageMark = [1,2,3,4, [5,10], [10]]
    let matchCondition = Object.assign(data._id, {
      create_time: { $gte: this.startTime, $lte: this.endTime }
    })

    let visitDepthData = {
      visit_users: {},
      visit_times: {}
    }
    for(let pi in pageMark) {
      let pageMarkCondition = {
        page_count: pageMark[pi]
      }
      
      if(Array.isArray(pageMark[pi])) {
        if(pageMark[pi].length === 2) {
          pageMarkCondition = {
            page_count: { $gte: pageMark[pi][0], $lte: pageMark[pi][1] }
          }
        } else {
          pageMarkCondition = {
            page_count: { $gt: pageMark[pi][0] }
          }
        }
      }

      //访问次数统计
      let searchCondition = Object.assign(matchCondition, pageMarkCondition)
      let vistRes = await this.getCollection(this.sessionLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        visit_count: 1,
        page_count: 1,
        create_time: 1
      })
      .match(searchCondition)
      .group({
        _id: {},
        total_visits: { $sum: "$visit_count" }
      })
      .end()
      console.log('vistResCondtion', JSON.stringify(searchCondition))
      console.log('vistRes', JSON.stringify(vistRes))
      let vistCount = 0
      if (vistRes.data.length > 0) {
        vistCount = vistRes.data[0].total_visits
      }

      //用户数统计
      let userRes = await this.getCollection(this.sessionLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        visit_count: 1,
        page_count: 1,
        create_time: 1
      })
      .match(searchCondition)
      .group({
        _id: {
          uuid: '$uuid'
        }
      })
      .group({
        _id: {},
        total_users: { $sum: 1 }
      })
      .end()
      console.log('userResCondtion', JSON.stringify(searchCondition))
      console.log('userRes', JSON.stringify(userRes))

      let userCount = 0
      if (userRes.data.length > 0) {
        userCount = userRes.data[0].total_users
      }

      let pageKey = 'p_' + (Array.isArray(pageMark[pi]) ? pageMark[pi][0] : pageMark[pi])
      visitDepthData.visit_users[pageKey] = userCount
      visitDepthData.visit_times[pageKey] = vistCount
    }

    //访问时长-用户数统计和访问次数
    let durationMark = [[0,2],[3,5],[6,10],[11,20], [21,30], [31,50], [51,100],[100]]
    let durationData = {
      visit_users: {},
      visit_times: {}
    }
    for(let di in durationMark) {

      let durationMarkCondition = {
        duration: durationMark[di]
      }
      
      if(Array.isArray(durationMark[di])) {
        if(durationMark[di].length === 2) {
          durationMarkCondition = {
            duration: { $gte: durationMark[di][0], $lte: durationMark[di][1] }
          }
        } else {
          durationMarkCondition = {
            duration: { $gt: durationMark[di][0] }
          }
        }
      }

      //访问次数统计
      let searchCondition = Object.assign(matchCondition, durationMarkCondition)
      let vistRes = await this.getCollection(this.sessionLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        visit_count: 1,
        duration: 1,
        create_time: 1
      })
      .match(searchCondition)
      .group({
        _id: {},
        total_visits: { $sum: "$visit_count" }
      })
      .end()
      console.log('vistRes', JSON.stringify(vistRes))
      let vistCount = 0
      if (vistRes.data.length > 0) {
        vistCount = vistRes.data[0].total_visits
      }

      //用户数统计
      let userRes = await this.getCollection(this.sessionLog.tableName)
      .aggregate()
      .project({
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        visit_count: 1,
        duration: 1,
        create_time: 1
      })
      .match(searchCondition)
      .group({
        _id: {
          uuid: '$uuid'
        }
      })
      .group({
        _id: {},
        total_users: { $sum: 1 }
      })
      .end()
      console.log('userRes', JSON.stringify(userRes))

      let userCount = 0
      if (userRes.data.length > 0) {
        userCount = userRes.data[0].total_users
      }

      let pageKey = 's_' + (Array.isArray(durationMark[di]) ? durationMark[di][0] : durationMark[di])
      durationData.visit_users[pageKey] = userCount
      durationData.visit_times[pageKey] = vistCount
    }

    //数据填充
    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      visit_depth_data: visitDepthData,
      duration_data: durationData,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }
    const res = await this.insert(this.tableName, insertParams)
    console.log('save res', JSON.stringify(res))
    if (res && res.id) {
      return {
        code: 0,
        msg: 'have done'
      }
    } else {
      console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
      return {
        code: 400,
        msg: 'log save error'
      }
    }

  }
}




// 统计入口
export default class Stat {
  constructor () {
  }

  // 数据上报
  async report (params) {
    let res = {
      code: 0,
      msg: 'success'
    }

    if (!params) {
      return {
        code: 200,
        msg: 'Invild params'
      }
    }

    //参数解析
    let requestParam = JSON.parse(params.requests)
    if(!requestParam || requestParam.length === 0) {
      return {
        code: 200,
        msg: 'Invild params'
      }
    }
    
    
    //日志填充
    for(let ri in requestParam) {
      let urlParams = this.parseUrlParams(requestParam[ri])
      if (!urlParams.ak) {
        return {
          code: 201,
          msg: 'Not found appid'
        }
      }

      if (!urlParams.lt) {
        return {
          code: 202,
          msg: 'Not found this log type'
        }
      }

      switch (parseInt(urlParams.lt)) {
        //会话日志
        case 1:
          let sessionLog = new SessionLog()
          res = await sessionLog.fill(urlParams)
          break

        //页面日志
        case 3:
        case 4:
        case 11:
          let pageLog = new PageLog()
          res = await pageLog.fill(urlParams)
          break

        //事件日志
        case 21:
          let eventLog = new EventLog()
          res = await eventLog.fill(urlParams)
          break

        //错误日志
        case 31:
          let errorLog = new ErrorLog()
          res = await errorLog.fill(urlParams)
          break
        default:
          console.log('Invalid type by param "lt:' + urlParams.lt + '"')
          break
      }
    }
    

    return res
  }

  //解析参数
  parseUrlParams(str) {
    if(!str || typeof str !== "string") {
      return str
    }
    return str.split("&").reduce((res, cur) => {
      const arr = cur.split("=")
      return Object.assign({[arr[0]]: arr[1]}, res)
    }, {})
  }


  //定时触发
  async statCron(cronConfig) {
    let dateTime = new DateTime()
    let timeInfo = dateTime.getTimeInfo(null, false)
    //console.log('timeInfo', JSON.stringify(timeInfo))
    cronConfig = cronConfig || getConfig('cron')
    //console.log('cronConfig', JSON.stringify(cronConfig))
    //数据跑批
    let res = null
    if(cronConfig && cronConfig.length > 0) {
      for(var mi in cronConfig) {
        let cronType = cronConfig[mi].type
        let cronTime = cronConfig[mi].time.split(' ')
        //console.log('cronTime', cronTime)
        if(cronTime.length === 4) {
          if(cronTime[0] !== '*') {
            if(timeInfo.nWeek == cronTime[0] && timeInfo.nHour == cronTime[2] && timeInfo.nMinutes == cronTime[3]) {
              console.log(cronType + '--week run')
              res = await this.stat(cronType, 'week')
            }
          } else if(cronTime[1] !== '*') {
            if(timeInfo.nDay == cronTime[1] && timeInfo.nHour == cronTime[2] && timeInfo.nMinutes == cronTime[3]) {
              console.log(cronType + '--month run')
              res = await this.stat(cronType, 'month')
            }
          } else if(cronTime[2] !== '*') {
            if(timeInfo.nHour == cronTime[2] && timeInfo.nMinutes == cronTime[3]) {
              console.log(cronType + '--day run')
              res = await this.stat(cronType, 'day')
            }
          } else if(cronTime[3] !== '*') {
            if(timeInfo.nMinutes == cronTime[3]) {
              console.log(cronType + '--hour run')
              res = await this.stat(cronType, 'hour')
            }
          }
        } else if (cronTime.length === 3) {
          if(cronTime[0] !== '*') {
            if(timeInfo.nWeek == cronTime[0] && timeInfo.nHour == cronTime[2]) {
              console.log(cronType + '--week run')
              res = await this.stat(cronType, 'week')
            }
          } else if(cronTime[1] !== '*') {
            if(timeInfo.nDay == cronTime[1] && timeInfo.nHour == cronTime[2]) {
              console.log(cronType + '--month run')
              res = await this.stat(cronType, 'month')
            }
          } else if(cronTime[2] !== '*') {
            if(timeInfo.nHour == cronTime[2]) {
              console.log(cronType + '--day run')
              res = await this.stat(cronType, 'day')
            }
          } else {
              console.log(cronType + '--hour run')
              res = await this.stat(cronType, 'hour')
          }
        }
      }
    }

    return {
      code: 0,
      msg: 'Task have done',
      lastCronResult: res
    }
  }

  // 统计相关
  async stat (parms) {
    const { type, dimension, date, reset } = parms
    let res = {
      code: 0,
      msg: 'success'
    }
    switch (type) {
      // 基础统计
      case 'stat':
        const resultStat = new StatResult()
        res = await resultStat.stat(dimension, date, reset)
        break
      // 留存统计
      case 'retention':
        const retentionStat = new StatResult()
        res = await retentionStat.retentionStat(dimension, date, reset)
        break
      //页面统计
      case 'page':
        let pageStat = new PageResult()
        res = await pageStat.stat(dimension, date, reset)
        break
      //事件统计
      case 'event':
        let eventStat = new EventResult()
        res = await eventStat.stat(dimension, date, reset)
        break
      //错误统计
      case 'error':
        let errorStat = new ErrorResult()
        res = await errorStat.stat(dimension, date, reset)
        break
      //用户忠诚度统计
      case 'loyalty':
        let loyaltyStat = new Loyalty()
        res = await loyaltyStat.stat(dimension, date, reset)
        break
    }
    return res
  }
}
